import parole, pygame
from parole.shader import *
from parole.input import *

import main, sim

class LineChooser(Shader):
    """
    A L{Shader} and UI event handler for letting the user scroll through a list
    of text lines and select one, invoking a callback for that line.
    Handles intput through a command map defined by a given command map
    config.
    """
    def __init__(self, font, lineCallbacks=None, yDist=10,
            flankers=(r'\PaleTurquoise >>', r'\PaleTurquoise <<'), commandCfg=None,
            fore_rgb=(255,255,255), name=None, alpha=255):
        """
        Creates a new LineChooser.
        @param lineCallbacks A list of C{(line,callback)}-tuples, where
        C{line} is a line of text and C{callback} is the associated functor.
        @param yDist The vertical distance to put between each line.
        """
        super(LineChooser, self).__init__(name or "LineChooser", alpha=alpha)
        self.font = font
        self.lineCallbacks = lineCallbacks or []
        self.lineShaders = {}
        self.yDist = yDist
        self.fore_rgb = fore_rgb
        self.flankers = (TextBlockPass(font, fore_rgb, text=flankers[0]),
                         TextBlockPass(font, fore_rgb, text=flankers[1]))
        self.flankers[0].update()
        self.flankers[1].update()
        self.selectedIdx = 0
        self.cmdMap = parole.input.CommandMap(commandCfg or
                parole.conf.interface.linechooser, self.handleCommand)
        self.arrange()

    def stringifyLineObj(self, obj):
        return str(obj)

    def arrange(self):
        """
        Creates a L{Pass} for each selectable line and positions it.
        """
        self.clearPasses()
        self.lineShaders.clear()
        maxW = 0
        for line,cb in self.lineCallbacks:
            tbLine = TextBlockPass(self.font, self.fore_rgb,
                    text=self.stringifyLineObj(line))
            tbLine.update()
            maxW = max(maxW, tbLine.width)
            self.lineShaders[id(line)] = tbLine
        flankersW = self.flankers[0].width + self.flankers[1].width + 10
        self.size = (maxW + flankersW,
                self.font.get_linesize()*len(self.lineShaders) + \
                self.yDist*(len(self.lineShaders) - 1))
        selfRect = pygame.Rect((0,0), self.size)
        y = 0
        for line, cb in self.lineCallbacks:
            tbLine = self.lineShaders[id(line)]
            lineRect = pygame.Rect((0,0), tbLine.size)
            lineRect.center = selfRect.center
            self.addPass(tbLine, pos=(lineRect.x, y))
            y += self.font.get_linesize() + self.yDist

        if self.selectedIdx >= len(self.lineCallbacks):
            self.selectedIdx = 0
        self.select(self.selectedIdx)

    def select(self, lineIdx):
        self.selectedIdx = lineIdx
        for f in self.flankers:
            if f in self.passes:
                self.remPass(f)
        y = lineIdx*self.font.get_linesize() + (lineIdx)*self.yDist
        self.addPass(self.flankers[0], (0,y))
        self.addPass(self.flankers[1], (self.width-self.flankers[1].width, y))

        for p in self.parents:
            if isinstance(p, ScrollView):
                viewX, viewY = p.offset
                if y-10 < viewY or y-10 > viewY + 380:
                    offY = y-10
                    if offY < viewY:
                        offY = max(0, offY)
                    else:
                        offY = min(offY, self.height - 392)
                    p.offset = (0,offY)


    def handleCommand(self, cmd):
        """
        UI event handling. 
        """
        if cmd == "line up":
            self.select(max(0, self.selectedIdx-1))
        elif cmd == "line down":
            self.select(min(len(self.lineCallbacks)-1, self.selectedIdx+1))
        elif cmd == "select":
            line,callback = self.lineCallbacks[self.selectedIdx]
            callback(line)

#========================================

class LineChooserFrame(Frame):
    def __init__(self, font, lineCallbacks=None, yDist=10,
            flankers=(r'\PaleTurquoise >>', r'\PaleTurquoise <<'), commandCfg=None,
            fore_rgb=(255,255,255), name=None, alpha=255, title=None):
        self.chooser = LineChooser(font, lineCallbacks, yDist, flankers,
                commandCfg, fore_rgb, name, alpha)
        self.chooser.update()
        #self.chBG = ColorField((0,0,128,200), (self.chooser.width+20,
        #    self.chooser.height+20))
        if title:
            txt = TextBlockPass(font, fore_rgb, text=title, wrap='word',
                    wrap_width = 300)
            txt.update()
        height = self.chooser.height+20+(title and txt.height+10 or 0)
        self.chBG = ColorField((128,0,0,200), (max(self.chooser.width,
            (title and txt.width or 0))+20+(height > 450 and 10 or 0),
            height))
        self.scroll = ScrollView((self.chBG.width, min(450, self.chBG.height)),
            vbar=VerticalScrollbar((128,128,0), (64,64,0), 10))
        self.scroll.addPass(self.chBG)

        if title:
            txtRect = pygame.Rect((0,0), txt.size)
            txtRect.centerx = self.chBG.rect.centerx
            self.scroll.addPass(txt, pos=(txtRect.x , 10))
            chRect = pygame.Rect((0,0), self.chooser.size)
            chRect.centerx = self.chBG.rect.centerx
            self.scroll.addPass(self.chooser, pos=(chRect.x,10+txtRect.h+10))
        else:
            self.scroll.addPass(self.chooser, pos=(10,10))

        borders = (None,None, 
                HorizontalBevel((200,200,0), (128,128,0), (64,64,0), 1, 3, 1), 
                HorizontalBevel((200,200,0), (128,128,0), (64,64,0), 1, 3, 1), 
                None, None, None, None)
        super(LineChooserFrame, self).__init__(borders)

        self.addPass(self.scroll, pos=(0,0))

#========================================

def fadeOut(time, rgb=(0,0,0), clearAfter=True):
    startTime = parole.time()
    r, g, b = rgb
    alpha = 0
    fader = ColorField((r,g,b,alpha),
            parole.display.getSurface().get_size())
    parole.display.scene.add(fader, pos=(0,0))
    parole.pushAnimation()
    while parole.time() - startTime < time:
        alpha = ((parole.time() - startTime)/float(time)) * 255.0
        fader.rgb = (r,g,b,int(alpha))
        yield alpha
    if clearAfter:
        parole.display.scene.removeAll()
        parole.display.clearSurface(parole.display.getSurface())
    else:
        parole.display.scene.remove(fader)
    yield
    parole.popAnimation()

#========================================

def messageBox(text, align='center', textWidth=274):
    font = parole.resource.getFont("fonts/Arial.ttf", 14)
    block = parole.shader.TextBlockPass(font, (255,255,255),
            wrap_width=textWidth, bg_rgb=(128,0,0), align=align, wrap='word')
    block.text = text
    block.update()
    sdr = parole.shader.Shader("FrameContents", 
            (block.width+20, block.height+20))
    sdr.addPass(parole.shader.ColorField((128,0,0), sdr.size))
    sdr.addPass(block, (10,10))
    mbox = parole.shader.Frame((parole.shader.VerticalBevel((0,0,0), 
        (128,128,128), (255,255,255),1, 2, 1),
        parole.shader.VerticalBevel((0,0,0), (128,129,128), (255,255,255), 1, 2, 1),
        parole.shader.HorizontalBevel((255,255,255), (128,128,128), (0,0,0), 1,
            2, 1),
        parole.shader.HorizontalBevel((255,255,255), (128,128,128), (0,0,0), 1,
            2, 1),
        None,None,None,None),
        contents=[sdr])
    mbox.update()
    parole.display.scene.add(mbox, pos=mbox.centeredPos())
    return mbox

#========================================

def commandKeys(command, group):
    keys = []
    for k,v in group.iteritems():
        if v == command:
            keys.append(k)
    return keys

#========================================

def init():
    global frameBorders
    frameBorders = (None,None, 
        HorizontalBevel((200,200,0), (128,128,0), (64,64,0), 1, 3, 1), 
        HorizontalBevel((200,200,0), (128,128,0), (64,64,0), 1, 3, 1), 
        None, None, None, None)

#========================================

class StatUpper(Frame):
    testify = \
"""
  You gain a level!
  Choose a stat to increase 
  ({\Gold %d} points left)
 
  [{\Gold %s}]\tStrength: %d
  [{\Gold %s}]\tDexterity: %d
  [{\Gold %s}]\tConstitution: %d
  [{\Gold %s}]\tPerception: %d
 
"""
    def __init__(self, points):
        self.points = points
        self.str, self.dex, self.con, self.per = 10, 10, 10, 10
        if main.player:
            self.str = main.player['strength']
            self.dex = main.player['dexterity']
            self.con = main.player['constitution']
            self.per = main.player['perception']

        self.txt = TextBlockPass(main.arial, (255,255,255))
        self.updateText()
        self.bg = ColorField((128,0,0,200), self.txt.size)

        super(StatUpper, self).__init__(frameBorders, contents=[self.bg,
            self.txt])
        self.update()

        self.cmdMap = parole.input.CommandMap(parole.conf.interface.statchooser, 
            self.handleCommand)

    def updateText(self):
        self.txt.text = self.testify % \
                (self.points, 
                 '{\White /}'.join(commandKeys("strength",
                     parole.conf.interface.statchooser.keypresses)),
                 self.str, 
                 '{\White /}'.join(commandKeys("dexterity",
                     parole.conf.interface.statchooser.keypresses)),
                 self.dex,
                 '{\White /}'.join(commandKeys("constitution",
                     parole.conf.interface.statchooser.keypresses)),
                 self.con, 
                 '{\White /}'.join(commandKeys("perception",
                     parole.conf.interface.statchooser.keypresses)),
                 self.per)
        self.txt.update()

    def push(self):
        parole.display.scene.add(self, pos=self.centeredPos())
        parole.pushUIEventHandler(self.cmdMap)
        self.active = True

    def pop(self):
        parole.display.scene.remove(self)
        parole.popUIEventHandler()
        self.active = False

    def handleCommand(self, cmd):
        if cmd == 'strength':
            self.str += 1
        elif cmd == 'dexterity':
            self.dex += 1
        elif cmd == 'constitution':
            self.con += 1
        elif cmd == 'perception':
            self.per += 1

        self.points -= 1
        if self.points <= 0:
            self.pop()

        self.updateText()

#========================================

class ModalBox(Frame):

    def __init__(self, text, font, onClose, textWidth=500):
        self.txt = parole.shader.TextBlockPass(font, (255,255,255),
                wrap_width=textWidth, wrap='word')
        self.txt.text = text
        self.txt.update()
        parole.debug('*** text size: %r', self.txt.size)
        self.bg = ColorField((128,0,0,200), (min(textWidth+20, self.txt.width+20),
                                             min(420, self.txt.height+20)))

        borders = (None,None, 
                HorizontalBevel((200,200,0), (128,128,0), (64,64,0), 1, 3, 1), 
                HorizontalBevel((200,200,0), (128,128,0), (64,64,0), 1, 3, 1), 
                None, None, None, None)
        super(ModalBox, self).__init__(borders, contents=[self.bg])

        self.scroll = None
        if self.txt.height > self.bg.height:
            self.scroll = ScrollView((self.bg.width+10, self.bg.height),
                vbar=VerticalScrollbar((128,128,0), (64,64,0), 10))
            self.scroll.addPass(self.txt)
            self.addPass(self.scroll, pos=(10,10))
        else:
            self.addPass(self.txt, pos=(10,10))
        self.update()
        self.instructions = TextBlockPass(font, (196,196,196))
        self.instructions.text = r"[%s to close]" % \
            r'{\Gold /}'.join(commandKeys("clear",
                parole.conf.interface.mainplay.keypresses))
        self.instructions.update()
        self.addPass(self.instructions, pos=(self.width/2 - \
            self.instructions.width/2, self.height))
        self.update()
        self.bg.size = self.size
        self.update()

        self.onClose = onClose
        self.cmdMap = parole.input.CommandMap(parole.conf.interface.mainplay, 
            self.handleCommand)

    def push(self):
        parole.display.scene.add(self, pos=self.centeredPos())
        parole.pushUIEventHandler(self.cmdMap)

    def pop(self):
        parole.display.scene.remove(self)
        parole.popUIEventHandler()

    def handleCommand(self, cmd):
        if cmd == "clear":
            self.pop()
            self.onClose()
        elif cmd == "walk north" and self.scroll:
            self.scroll.scrollPixels(0, -10)
        elif cmd == "walk south" and self.scroll:
            self.scroll.scrollPixels(0, 10)
        elif cmd == "view up" and self.scroll:
            self.scroll.scrollPixels(0, -100)
        elif cmd == "view down" and self.scroll:
            self.scroll.scrollPixels(0, 100)
                    
#========================================

class DirectionChooser(object):
    """
    Waits for a direction command. Calls handleFn with displacement of direction
    received. If canceled, pops itself and calls cancelFn if not None.
    Up to user to display a prompt before pushing the chooser.
    """
    def __init__(self, handleFn, cancelFn=None):
        self.handleCancel = (handleFn, cancelFn)
        self.cmdMap = parole.input.CommandMap(parole.conf.interface.mainplay, 
            self.handleCommand)

    def push(self):
        parole.pushUIEventHandler(self.cmdMap)

    def pop(self):
        parole.popUIEventHandler()

    def handleCommand(self, cmd):
        if cmd.startswith('walk '):
            dir = cmd.split()[1:][0]
            disp = main.displacement(dir)
            self.pop()
            self.handleCancel[0](disp)
        elif cmd == 'clear':
            self.pop()
            if self.handleCancel[1]:
                self.handleCancel[1]()

#========================================

class ObjectChooser(object):
    def __init__(self, objs, handleFn, cancelFn=None, title=None):
        self.objs = objs
        self.handleCancel = (handleFn, cancelFn)

        choices = []
        for obj in objs:
            choices.append((obj, self.doChoose))

        self.chFrame = LineChooserFrame(main.arial, 
                choices,
                title=title)
        self.chFrame.update()
        self.cmdMap = parole.input.CommandMap(parole.conf.interface.mainplay, 
            self.handleCommand, peek=True)

    def push(self):
        parole.display.scene.add(self.chFrame, pos=self.chFrame.centeredPos())
        parole.pushUIEventHandler((self.cmdMap, self.chFrame.chooser.cmdMap))

    def pop(self):
        parole.display.scene.remove(self.chFrame)
        parole.popUIEventHandler()

    def doChoose(self, obj):
        parole.debug('Chose %s', obj)
        self.pop()
        self.handleCancel[0](obj)

    def handleCommand(self, cmd):
        if cmd == 'clear':
            self.pop()
            if self.handleCancel[1]:
                self.handleCancel[1]()

#========================================

class StatusBox(Frame, sim.Listener):
    row1Txt = r"{\Gold HP}:%s/%s {\Gold STM}:%s/%s {\Gold SPD}:%s {\Gold EV}:%s {\Gold PV}:%s {\Gold HIT}:%s {\Gold DMG}:%s"
    row2Txt = r"{\Gold STR}:%s {\Gold DEX}:%s {\Gold CON}:%s {\Gold PER}:%s {\Gold XP}:%s/%s {\Gold WGT}:%s/%s"
    def __init__(self, font, creature):
        borders = (
                VerticalBevel((200,200,0), (128,128,0), (64,64,0), 1, 3, 1), 
                VerticalBevel((200,200,0), (128,128,0), (64,64,0), 1, 3, 1), 
                None,
                HorizontalBevel((200,200,0), (128,128,0), (64,64,0), 1, 3, 1), 
                None, None, None, None)
        Frame.__init__(self, borders)

        self.font = font
        self.creature = creature
        self.row1 = TextBlockPass(font, (255,255,255))
        self.row2 = TextBlockPass(font, (255,255,255))
        self.bg = ColorField((164,0,0,128), (10,10))
        self.addPass(self.bg)
        self.addPass(self.row1, pos=(5,5))
        self.addPass(self.row2, pos=(5,5+font.get_linesize()+2))

    def update(self, *args, **kwargs):
        if not self.dirty:
            return
        parole.debug('Updating StatusBox')

        c = self.creature
        self.row1.text = self.row1Txt % (c['hp'], c['maxHP'], c['stamina'],
                c['maxStamina'], c['speed'], c['ev'], c['pv'], c['hit'],
                c['damageSkew'])
        self.row1.update()
        self.row2.text = self.row2Txt % (c['strength'], c['dexterity'],
                c['constitution'], c['perception'], c.level, c.xp,
                c.inventoryWeight(), c['capacity'])
        self.row2.update()
        self.bg.size = (400,#max(self.row1.width, self.row2.width) + 10, 
                self.row1.height + self.row2.height + 12)

        Frame.update(self, *args, **kwargs)

    def listen(self, event):
        if event.id in ('set stat', 'increase stat', 'decrease stat'):
            creature, statname, value = event.args
            if creature is self.creature:
                parole.debug('Touching status box')
                self.touch()
        elif event.id in ('enter inventory', 'leave inventory'):
            item, creature = event.args
            if creature is self.creature:
                parole.debug('Touching status box')
                self.touch()



